CMS Compliance
Implementations MUST support key transport, key agreement, and
previously distributed symmetric key-encryption keys, as represented
by ktri, kari, and kekri, respectively.
Implementations MAY support the password-based key management as represented by pwri.
Implementations MAY support any other key management technique
such as Boneh-Franklin and Boneh-Boyen Identity-Based Encryption (RFC 5409)
or other SYNRC encryption techniques such as KYBER Key Transport variations (LAMPS-WG)
for post-quantum cryptography (PQC).
IETF follow up (SMIME-WG): 5990, 5911, 5750--5754, 5652, 5408, 5409, 5275, 5126,
5035, 4853, 4490, 4262, 4134, 4056, 4010, 3850, 3851, 3852,
3854, 3855, 3657, 3560, 3565, 3537, 3394, 3369, 3370, 3274,
3114, 3278, 3218, 3211, 3217, 3183, 3185, 3125--3126, 3058,
2984, 2876, 2785, 2630, 2631, 2632, 2633, 5083, 5084, 2634.
Compatibility: Erlang SSL, LibreSSL CMS, OpenSSL CMS, GnuPG S/MIME.
Elixir.CMS.ebin
X.509 CMS Cryptographic Message Syntax specification for
RSA (Key Transport),
ECC (Key Agreement),
KEK (Key Encryption Key) disciplines for Erlang/OTP never
heartbleeded (!) CRYPTO and SSL applcations. Implemented as CMS module of CA applcation.
defmodule CMS do
def decrypt(cms, {schemeOID, privateKeyBin}) do
{_,{:ContentInfo,_,{:EnvelopedData,_,_,x,y,_}}} = cms
{:EncryptedContentInfo,_,{_,encOID,{_,<<_::16,iv::binary>>}},data} = y
case :proplists.get_value(:kari, x, []) do
[] -> case :proplists.get_value(:ktri, x, []) do
[] -> case :proplists.get_value(:kekri, x, []) do
[] -> case :proplists.get_value(:pwri, x, []) do
[] -> {:error, "Unknown Other Recepient Info"}
pwri -> pwri(pwri, privateKeyBin, encOID, data, iv) end
kekri -> kekri(kekri, privateKeyBin, encOID, data, iv) end
ktri -> ktri(ktri, privateKeyBin, encOID, data, iv) end
kari -> kari(kari, privateKeyBin, schemeOID, encOID, data, iv) end
end
end
CMS-KARI-ECC
IETF 3278:2002 Use of Elliptic Curve Cryptography (ECC) Algorithms
in Cryptographic Message Syntax (CMS) with support of
Suite B IETF 5008:2007, 6318:2011.
$ openssl cms -decrypt -in encrypted.txt -inkey client.key -recip client.pem
$ openssl cms -encrypt -aes256 -in message.txt -out encrypted.txt \
-recip client.pem -keyopt ecdh_kdf_md:sha256
# CMS Codec KARI: ECC+KDF/ECB+AES/KW+256/CBC
def map(:'dhSinglePass-stdDH-sha512kdf-scheme'), do: :sha512
def map(:'dhSinglePass-stdDH-sha384kdf-scheme'), do: :sha384
def map(:'dhSinglePass-stdDH-sha256kdf-scheme'), do: :sha256
def eccCMS(ukm, bit), do:
:'CMSECCAlgs-2009-02'.encode(:'ECC-CMS-SharedInfo', sharedInfo(ukm, bit))
def sharedInfo(ukm, len), do: {:'ECC-CMS-SharedInfo',
{:'KeyWrapAlgorithm',{2,16,840,1,101,3,4,1,45},:asn1_NOVALUE}, ukm, <>}
def kari(kari, privateKeyBin, schemeOID, encOID, data, iv) do
{_,:v3,{_,{_,_,publicKey}},ukm,{_,kdfOID,_},[{_,_,encryptedKey}]} = kari
{scheme,_} = CA.ALG.lookup(schemeOID)
{kdf,_} = CA.ALG.lookup(kdfOID)
{enc,_} = CA.ALG.lookup(encOID)
sharedKey = :crypto.compute_key(:ecdh,publicKey,privateKeyBin,scheme)
{_,payload} = eccCMS(ukm, 256)
derived = KDF.derive(map(kdf), sharedKey, 32, payload)
unwrap = CA.AES.KW.unwrap(encryptedKey, derived)
res = CA.AES.decrypt(enc, data, unwrap, iv)
{:ok, res}
end
def testDecryptECC(), do: CA.CMS.decrypt(testECC(), testPrivateKeyECC())
def testECC() do
{:ok,base} = :file.read_file "priv/certs/encrypted.txt"
[_,s] = :string.split base, "\n\n"
x = :base64.decode s
:'CryptographicMessageSyntax-2010'.decode(:ContentInfo, x)
end
def testPrivateKeyECC() do
privateKey = :public_key.pem_entry_decode(pem("priv/certs/client.key"))
{:'ECPrivateKey',_,privateKeyBin,{:namedCurve,schemeOID},_,_} = privateKey
{schemeOID,privateKeyBin}
end
CMS-KEKRI-KEK
Key Encryption Key Recipient Info as defined by CMS
IETF 5652:2009, 3852:2004, 3369:2002, 2630:1999.
$ openssl cms -encrypt -secretkeyid 07 \
-secretkey 0123456789ABCDEF0123456789ABCDEF \
-aes256 -in message.txt -out encrypted2.txt
$ openssl cms -decrypt -in encrypted2.txt -secretkeyid 07 \
-secretkey 0123456789ABCDEF0123456789ABCDEF
# CMS Codec KEKRI: KEK+AES-KW+CBC
def kekri(kekri, privateKeyBin, encOID, data, iv) do
{:'KEKRecipientInfo',_vsn,_,{_,kea,_},encryptedKey} = kekri
_ = CA.ALG.lookup(kea)
{enc,_} = CA.ALG.lookup(encOID)
unwrap = CA.AES.KW.unwrap(encryptedKey,privateKeyBin)
res = CA.AES.decrypt(enc, data, unwrap, iv)
{:ok, res}
end
def testDecryptKEK(), do: CA.CMS.decrypt(testKEK(), testPrivateKeyKEK())
def testPrivateKeyKEK() do
{:kek, :binary.decode_hex("0123456789ABCDEF0123456789ABCDEF")}
end
def testKEK() do
{:ok,base} = :file.read_file "priv/certs/encrypted2.txt"
[_,s] = :string.split base, "\n\n"
x = :base64.decode s
:'CryptographicMessageSyntax-2010'.decode(:ContentInfo, x)
end
CMS-KTRI-RSA
The very first CMS IETF 3852:1999.
$ gpgsm --list-keys
$ gpgsm --list-secret-keys
$ gpgsm -r 0xD3C8F78A -e CNAME > cms.bin
$ gpgsm -u 0xD3C8F78A -d cms.bin
$ gpgsm --export-secret-key-p12 0xD3C8F78A > key.bin
$ openssl pkcs12 -in key.bin -nokeys -out public.pem
$ openssl pkcs12 -in key.bin -nocerts -nodes -out private.pem
# CMS Codec KTRI: RSA+RSAES-OAEP
def ktri(ktri, privateKeyBin, encOID, data, iv) do
{:'KeyTransRecipientInfo',_vsn,_,{_,schemeOID,_},key} = ktri
{:rsaEncryption,_} = CA.ALG.lookup schemeOID
{enc,_} = CA.ALG.lookup(encOID)
sessionKey = :public_key.decrypt_private(key, privateKeyBin)
res = CA.AES.decrypt(enc, data, sessionKey, iv)
{:ok, res}
end
def testDecryptRSA(), do: CA.CMS.decrypt(testRSA(), testPrivateKeyRSA())
def testPrivateKeyRSA() do
{:ok,bin} = :file.read_file("priv/rsa-cms.key")
pki = :public_key.pem_decode(bin)
[{:PrivateKeyInfo,_,_}] = pki
rsa = :public_key.pem_entry_decode(hd(pki))
{:'RSAPrivateKey',:'two-prime',_n,_e,_d,_,_,_,_,_,_} = rsa
{:rsaEncryption,rsa}
end
def testRSA() do
{:ok,x} = :file.read_file "priv/rsa-cms.bin"
:'CryptographicMessageSyntax-2010'.decode(:ContentInfo, x)
end
KDF
KDF (MD5: 128, SHA: 160—512) and HKDF (HMAC) Key Derive functions used in ECC CMS schemes as
of NIST SP 800-108r1 .
defmodule KDF do
def hl(:md5), do: 16
def hl(:sha), do: 20
def hl(:sha224), do: 28
def hl(:sha256), do: 32
def hl(:sha384), do: 48
def hl(:sha512), do: 64
def derive(h, d, len, x) do
:binary.part(:lists.foldr(fn i, a ->
:crypto.hash(h, d <> <> <> x) <> a
end, <<>>, :lists.seq(1,round(Float.ceil(len/hl(h))))), 0, len)
end
end
AES-KW
AES Key Wrap function is applicable to keys of 128/192/256
bit using AES-ECB encoding as of RFC 5649:2009 Advanced
Encryption Standard (AES) Key Wrap with Padding Algorithm.
-define(MSB64, 1/unsigned-big-integer-unit:64).
-define(DEFAULT_IV, << 16#A6A6A6A6A6A6A6A6:?MSB64 >>).
unwrap(CipherText, KEK) -> unwrap(CipherText, KEK, ?DEFAULT_IV).
unwrap(CipherText, KEK, IV)
when (byte_size(CipherText) rem 8) =:= 0
andalso (bit_size(KEK) =:= 128
orelse bit_size(KEK) =:= 192
orelse bit_size(KEK) =:= 256) ->
BlockCount = (byte_size(CipherText) div 8) - 1,
IVSize = byte_size(IV),
case do_unwrap(CipherText, 5, BlockCount, KEK) of
<< IV:IVSize/binary, PlainText/binary >> ->
PlainText;
_ ->
erlang:error({badarg, [CipherText, KEK, IV]})
end.
codec(128) -> aes_128_ecb;
codec(192) -> aes_192_ecb;
codec(256) -> aes_256_ecb.
do_unwrap(Buffer, J, _BlockCount, _KEK) when J < 0 -> Buffer;
do_unwrap(Buffer, J, BlockCount, KEK) ->
do_unwrap(do_unwrap(Buffer, J, BlockCount, BlockCount, KEK),
J - 1, BlockCount, KEK).
do_unwrap(Buffer, _J, I, _BlockCount, _KEK) when I < 1 -> Buffer;
do_unwrap(<< A0:?MSB64, Rest/binary >>, J, I, BlockCount, KEK) ->
HeadSize = (I - 1) * 8,
<< Head:HeadSize/binary, B0:8/binary, Tail/binary >> = Rest,
Round = (BlockCount * J) + I,
A1 = A0 bxor Round,
Data = << A1:?MSB64, B0/binary >>,
<< A2:8/binary, B1/binary >>
= crypto:crypto_one_time(codec(bit_size(KEK)),
KEK, ?DEFAULT_IV, Data, [{encrypt,false}]),
do_unwrap(<< A2/binary, Head/binary, B1/binary,
Tail/binary >>, J, I - 1, BlockCount, KEK).
AES-256
All AES-256 flavours are implemented for a wide range of ECC Key Agreement schemes.
def decrypt(crypto_codec, data, key, iv \\ :crypto.strong_rand_bytes(16))
def decrypt(:'id-aes256-ECB', data, key, iv), do: decryptAES256ECB(data, key, iv)
def decrypt(:'id-aes256-CBC', data, key, iv), do: decryptAES256CBC(data, key, iv)
def decrypt(:'id-aes256-GCM', data, key, iv), do: decryptAES256GCM(data, key, iv)
def decrypt(:'id-aes256-CCM', data, key, iv), do: decryptAES256CCM(data, key, iv)
def test() do
[
check_SECP384R1_GCM256(),
check_X25519_GCM256(),
check_C2PNB368w1_GCM256(),
check_BrainPoolP512t1_GCM256(),
check_BrainPoolP512t1_GCM256(),
check_SECT571_GCM256(),
check_X448_GCM256(),
check_X448_CBC256(),
check_X448_ECB256(),
]
end